﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;

namespace AvalonDock
{
    /// <summary>
    /// Represents the base class for <see cref="DockableFloatingWindow"/> and <see cref="DocumentFloatingWindow"/> classes
    /// </summary>
    /// <remarks>
    /// Provides base services for floating windows
    /// </remarks>
    public abstract class FloatingWindow : AvalonDockWindow
    {
        internal void AddLogicalChild(FrameworkElement frameworkElement)
        {
            base.AddLogicalChild(frameworkElement);
        }

        internal void RemoveLogicalChild(FrameworkElement frameworkElement)
        {
            base.RemoveLogicalChild(frameworkElement);
        }

        /// <summary>
        /// The _manager.
        /// </summary>
        private readonly DockingManager _manager;

        /// <summary>
        /// Initializes static members of the <see cref="FloatingWindow"/> class.
        /// </summary>
        static FloatingWindow()
        {
            ShowInTaskbarProperty.OverrideMetadata(typeof(FloatingWindow), new FrameworkPropertyMetadata(false));
            WindowStyleProperty.OverrideMetadata(typeof(FloatingWindow), new FrameworkPropertyMetadata(WindowStyle.ToolWindow));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FloatingWindow"/> class.
        /// </summary>
        internal FloatingWindow()
        {
            Activated += (sender, args) =>
                             {
                                 if (_manager.ActiveContent != null)
                                 {
                                     Keyboard.ClearFocus();
                                     FocusManager.SetFocusedElement(FocusManager.GetFocusScope(_manager.ParentWindow),
                                                                    _manager.ParentWindow);
                                 }
                             };
   
            Loaded += OnLoaded;
            Unloaded += OnUnloaded;

            SizeChanged += FloatingWindow_SizeChanged;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FloatingWindow"/> class.
        /// </summary>
        /// <param name="manager">
        /// The manager.
        /// </param>
        internal FloatingWindow(DockingManager manager)
            : this()
        {
            // save manager ref
            _manager = manager;
        }

        /// <summary>
        /// Gets Manager.
        /// </summary>
        internal DockingManager Manager
        {
            get { return _manager; }
        }

        /// <summary>
        /// Gets HostedPane.
        /// </summary>
        public Pane HostedPane
        {
            get { return Content as Pane; }
        }

        #region ContentTitle

        /// <summary>
        /// ContentTitle Read-Only Dependency Property
        /// </summary>
        private static readonly DependencyPropertyKey ContentTitlePropertyKey
            = DependencyProperty.RegisterReadOnly("ContentTitle", typeof (object), typeof (FloatingWindow), 
                                                  new FrameworkPropertyMetadata((object) null));

        /// <summary>
        /// The content title property.
        /// </summary>
        public static readonly DependencyProperty ContentTitleProperty
            = ContentTitlePropertyKey.DependencyProperty;

        /// <summary>
        /// Gets the ContentTitle property.  This dependency property 
        /// indicates title of the content currectly hosted in the floating window.
        /// </summary>
        public object ContentTitle
        {
            get { return GetValue(ContentTitleProperty); }
        }

        /// <summary>
        /// Provides a secure method for setting the ContentTitle property.  
        /// This dependency property indicates title of the content currectly hosted in the floating window.
        /// </summary>
        /// <param name="value">
        /// The new value for the property.
        /// </param>
        protected void SetContentTitle(object value)
        {
            SetValue(ContentTitlePropertyKey, value);
        }


        /// <summary>
        /// The update content title.
        /// </summary>
        private void UpdateContentTitle()
        {
            if (HostedPane == null)
                return;

            var cnt = HostedPane.SelectedItem as ManagedContent;
            if (cnt != null)
                SetContentTitle(cnt.Title);
        }

        #endregion

        /// <summary>
        /// The floating window_ size changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void FloatingWindow_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (HostedPane != null)
            {
                foreach (ManagedContent c in HostedPane.Items)
                    c.FloatingWindowSize = new Size(Width, Height);

                ResizingPanel.SetEffectiveSize(HostedPane, new Size(Width, Height));
            }
        }

        /// <summary>
        /// The on content changed.
        /// </summary>
        /// <param name="oldContent">
        /// The old content.
        /// </param>
        /// <param name="newContent">
        /// The new content.
        /// </param>
        protected override void OnContentChanged(object oldContent, object newContent)
        {
            base.OnContentChanged(oldContent, newContent);

            if (_manager != null)
            {
                _manager.RegisterFloatingWindow(this);
                _manager.RefreshContents();
            }

            UpdateContentTitle();
        }


        /// <summary>
        /// The on end drag.
        /// </summary>
        internal virtual void OnEndDrag()
        {
        }

        /// <summary>
        /// The on show selection box.
        /// </summary>
        internal virtual void OnShowSelectionBox()
        {
        }

        /// <summary>
        /// The on hide selection box.
        /// </summary>
        internal virtual void OnHideSelectionBox()
        {
        }

        /// <summary>
        /// The clone pane.
        /// </summary>
        /// <returns>
        /// </returns>
        public abstract Pane ClonePane();

        /// <summary>
        /// The check contents.
        /// </summary>
        internal void CheckContents()
        {
            if (HostedPane == null)
                return;

            ManagedContent[] cntsToCheck = HostedPane.Items.Cast<ManagedContent>().ToArray();

            cntsToCheck.ForEach(cnt =>
                                    {
                                        if (cnt.Manager == null ||
                                            cnt.Manager != Manager ||
                                            (!cnt.Manager.DockableContents.Contains(cnt as DockableContent) &&
                                             !cnt.Manager.Documents.Contains(cnt as DocumentContent)))
                                            cnt.ContainerPane.RemoveContent(cnt);
                                    });
        }

        #region Enable/Disable window Close Button

        /// <summary>
        /// The m f_ byposition.
        /// </summary>
        private const int MF_BYPOSITION = 0x400;

        /// <summary>
        /// The m f_ enabled.
        /// </summary>
        private const int MF_ENABLED = 0x0000;

        /// <summary>
        /// The m f_ grayed.
        /// </summary>
        private const int MF_GRAYED = 0x0001;

        /// <summary>
        /// The m f_ disabled.
        /// </summary>
        private const int MF_DISABLED = 0x0002;

        /// <summary>
        /// The get system menu.
        /// </summary>
        /// <param name="hWnd">
        /// The h wnd.
        /// </param>
        /// <param name="bRevert">
        /// The b revert.
        /// </param>
        /// <returns>
        /// </returns>
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr GetSystemMenu(IntPtr hWnd, int bRevert);

        /// <summary>
        /// The get menu item count.
        /// </summary>
        /// <param name="hMenu">
        /// The h menu.
        /// </param>
        /// <returns>
        /// The get menu item count.
        /// </returns>
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern int GetMenuItemCount(IntPtr hMenu);

        /// <summary>
        /// The draw menu bar.
        /// </summary>
        /// <param name="hWnd">
        /// The h wnd.
        /// </param>
        /// <returns>
        /// The draw menu bar.
        /// </returns>
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern int DrawMenuBar(IntPtr hWnd);

        /// <summary>
        /// The enable menu item.
        /// </summary>
        /// <param name="hMenu">
        /// The h menu.
        /// </param>
        /// <param name="uIDEnableItem">
        /// The u id enable item.
        /// </param>
        /// <param name="uEnable">
        /// The u enable.
        /// </param>
        /// <returns>
        /// The enable menu item.
        /// </returns>
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern bool EnableMenuItem(IntPtr hMenu, int uIDEnableItem, int uEnable);

        /// <summary>
        /// The enable x button.
        /// </summary>
        private void EnableXButton()
        {
            var helper = new WindowInteropHelper(this);
            IntPtr hMenu = GetSystemMenu(helper.Handle, 0);

            int menuItemCount = GetMenuItemCount(hMenu);

            EnableMenuItem(hMenu, menuItemCount - 1, MF_BYPOSITION | MF_ENABLED);
            DrawMenuBar(helper.Handle);
        }

        /// <summary>
        /// The disable x button.
        /// </summary>
        private void DisableXButton()
        {
            var helper = new WindowInteropHelper(this);
            IntPtr hMenu = GetSystemMenu(helper.Handle, 0);

            int menuItemCount = GetMenuItemCount(hMenu);

            EnableMenuItem(hMenu, menuItemCount - 1, MF_BYPOSITION | MF_DISABLED | MF_GRAYED);
            DrawMenuBar(helper.Handle);
        }

        #endregion

        #region Non-Client area management

        /// <summary>
        /// The w m_ move.
        /// </summary>
        protected const int WM_MOVE = 0x0003;

        /// <summary>
        /// The w m_ size.
        /// </summary>
        protected const int WM_SIZE = 0x0005;

        /// <summary>
        /// The w m_ ncmousemove.
        /// </summary>
        protected const int WM_NCMOUSEMOVE = 0xa0;

        /// <summary>
        /// The w m_ nclbuttondown.
        /// </summary>
        protected const int WM_NCLBUTTONDOWN = 0xA1;

        /// <summary>
        /// The w m_ nclbuttonup.
        /// </summary>
        protected const int WM_NCLBUTTONUP = 0xA2;

        /// <summary>
        /// The w m_ nclbuttondblclk.
        /// </summary>
        protected const int WM_NCLBUTTONDBLCLK = 0xA3;

        /// <summary>
        /// The w m_ ncrbuttondown.
        /// </summary>
        protected const int WM_NCRBUTTONDOWN = 0xA4;

        /// <summary>
        /// The w m_ ncrbuttonup.
        /// </summary>
        protected const int WM_NCRBUTTONUP = 0xA5;

        /// <summary>
        /// The htcaption.
        /// </summary>
        protected const int HTCAPTION = 2;

        /// <summary>
        /// The s c_ move.
        /// </summary>
        protected const int SC_MOVE = 0xF010;

        /// <summary>
        /// The w m_ syscommand.
        /// </summary>
        protected const int WM_SYSCOMMAND = 0x0112;

        #region Load/Unload window events

        /// <summary>
        /// The on loaded.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        protected void OnLoaded(object sender, EventArgs e)
        {
            var wih = new WindowInteropWrapper(this);

            // wih.WindowActivating += (s, ce) => ce.Cancel = true;//prevent window activating
            wih.FilterMessage += FilterMessage;

            if (HostedPane.Items.Count > 0)
            {
                var cntHosted = HostedPane.Items[0] as ManagedContent;
                if (!cntHosted.IsCloseable)
                {
                    DisableXButton();
                }
            }

            ManagedContent tmp = _manager.ActiveContent;
            _manager.ActiveContent = null;
            _manager.ActiveContent = tmp;
            Activate();
        }

        /// <summary>
        /// The on unloaded.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        protected void OnUnloaded(object sender, EventArgs e)
        {
        }

        #endregion

        /// <summary>
        /// The filter message.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        protected virtual void FilterMessage(object sender, FilterMessageEventArgs e)
        {
            if (e.Handled)
                return;

            if (Manager == null)
                return;

            switch (e.Msg)
            {
                case WM_SIZE:
                case WM_MOVE:
                    break;
                case WM_NCRBUTTONDOWN: // Right button click on title area -> show context menu
                    if (e.WParam.ToInt32() == HTCAPTION)
                    {
                        var x = (short) (e.LParam.ToInt32() & 0xFFFF);
                        var y = (short) (e.LParam.ToInt32() >> 16);
                        OpenContextMenu(null, new Point(x, y));
                        e.Handled = true;
                    }

                    break;
                case WM_NCRBUTTONUP: // set as handled right button click on title area (after showing context menu)
                    if (e.WParam.ToInt32() == HTCAPTION)
                    {
                        e.Handled = true;
                    }

                    break;
            }
        }

        #endregion

        #region Floating/dockable window state

        /// <summary>
        /// Redock contained <see cref="ManagedContent"/> object to the <see cref="DcokingManager"/>
        /// </summary>
        public virtual void Dock()
        {
        }

        #endregion

        #region Move/Resize

        /// <summary>
        /// The on apply template.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            var resLeftAnchor = GetTemplateChild("PART_LeftAnchor") as Resizer;
            var resTopAnchor = GetTemplateChild("PART_TopAnchor") as Resizer;
            var resBottomAnchor = GetTemplateChild("PART_BottomAnchor") as Resizer;
            var resRightAnchor = GetTemplateChild("PART_RightAnchor") as Resizer;

            var resLeftTopAnchor = GetTemplateChild("PART_LeftTopAnchor") as Resizer;
            var resLeftBottomAnchor = GetTemplateChild("PART_LeftBottomAnchor") as Resizer;

            var resRightTopAnchor = GetTemplateChild("PART_RightTopAnchor") as Resizer;
            var resRightBottomAnchor = GetTemplateChild("PART_RightBottomAnchor") as Resizer;

            // Resizer resMoveAnchor = GetTemplateChild("PART_MoveAnchor") as Resizer;
            var resMoveAnchor = GetTemplateChild("PART_MoveAnchor") as Border;

            if (resLeftAnchor != null)
                resLeftAnchor.DragDelta += (s, e) =>
                                               {
                                                   double delta = Math.Max(MinWidth, Width - e.HorizontalChange) - Width;
                                                   Left -= delta;
                                                   Width += delta;
                                               };
            if (resRightAnchor != null)
                resRightAnchor.DragDelta += (s, e) =>
                                                {
                                                    double delta = Math.Max(MinWidth, Width + e.HorizontalChange) -
                                                                   Width;
                                                    Width += delta;
                                                };
            if (resTopAnchor != null)
                resTopAnchor.DragDelta += (s, e) =>
                                              {
                                                  double delta = Math.Max(MinHeight, Height - e.VerticalChange) - Height;
                                                  Top -= delta;
                                                  Height += delta;
                                              };
            if (resBottomAnchor != null)
                resBottomAnchor.DragDelta += (s, e) =>
                                                 {
                                                     double delta = Math.Max(MinHeight, Height + e.VerticalChange) -
                                                                    Height;
                                                     Height += delta;
                                                 };

            if (resLeftTopAnchor != null)
                resLeftTopAnchor.DragDelta += (s, e) =>
                                                  {
                                                      double delta = Math.Max(MinWidth, Width - e.HorizontalChange) -
                                                                     Width;
                                                      Left -= delta;
                                                      Width += delta;

                                                      delta = Math.Max(MinHeight, Height - e.VerticalChange) - Height;
                                                      Top -= delta;
                                                      Height += delta;
                                                  };
            if (resLeftBottomAnchor != null)
                resLeftBottomAnchor.DragDelta += (s, e) =>
                                                     {
                                                         double delta = Math.Max(MinWidth, Width - e.HorizontalChange) -
                                                                        Width;
                                                         Left -= delta;
                                                         Width += delta;

                                                         delta = Math.Max(MinHeight, Height + e.VerticalChange) - Height;
                                                         Height += delta;
                                                     };
            if (resRightTopAnchor != null)
                resRightTopAnchor.DragDelta += (s, e) =>
                                                   {
                                                       double delta = Math.Max(MinWidth, Width + e.HorizontalChange) -
                                                                      Width;
                                                       Width += delta;

                                                       delta = Math.Max(MinHeight, Height - e.VerticalChange) - Height;
                                                       Top -= delta;
                                                       Height += delta;
                                                   };
            if (resRightBottomAnchor != null)
                resRightBottomAnchor.DragDelta += (s, e) =>
                                                      {
                                                          double delta =
                                                              Math.Max(MinWidth, Width + e.HorizontalChange) - Width;
                                                          Width += delta;

                                                          delta = Math.Max(MinHeight, Height + e.VerticalChange) -
                                                                  Height;
                                                          Height += delta;
                                                      };

            if (resMoveAnchor != null)
            {
                bool isMouseDown = false;
                var ptStartDrag = new Point();
                resMoveAnchor.MouseLeftButtonDown += (s, e) =>
                                                         {
                                                             isMouseDown = true;
                                                             ptStartDrag = e.GetPosition(s as IInputElement);
                                                             resMoveAnchor.CaptureMouse();
                                                         };

                resMoveAnchor.MouseMove += (s, e) =>
                                               {
                                                   if (isMouseDown && resMoveAnchor.IsMouseCaptured)
                                                   {
                                                       Point ptMouseMove = e.GetPosition(s as IInputElement);
                                                       if (Math.Abs(ptMouseMove.X - ptStartDrag.X) >
                                                           SystemParameters.MinimumHorizontalDragDistance ||
                                                           Math.Abs(ptMouseMove.Y - ptStartDrag.Y) >
                                                           SystemParameters.MinimumVerticalDragDistance)
                                                       {
                                                           isMouseDown = false;
                                                           resMoveAnchor.ReleaseMouseCapture();
                                                           HandleMove();
                                                       }
                                                   }
                                               };

                resMoveAnchor.MouseLeftButtonUp += (s, e) =>
                                                       {
                                                           isMouseDown = false;
                                                           resMoveAnchor.ReleaseMouseCapture();
                                                       };
            }

            var pupupButton = GetTemplateChild("PART_ShowContextMenuButton") as FrameworkElement;

            if (pupupButton != null)
                pupupButton.MouseLeftButtonDown +=
                    (s, e) => { e.Handled = OpenContextMenu(s as Border, e.GetPosition(s as IInputElement)); };

            var titleAnchor = GetTemplateChild("PART_MoveAnchor") as FrameworkElement;
            if (titleAnchor != null)
                titleAnchor.MouseRightButtonDown +=
                    (s, e) => { e.Handled = OpenContextMenu(s as Border, e.GetPosition(s as IInputElement)); };


            base.OnApplyTemplate();
        }

        /// <summary>
        /// The open context menu.
        /// </summary>
        /// <param name="popupButton">
        /// The popup button.
        /// </param>
        /// <param name="ptMouse">
        /// The pt mouse.
        /// </param>
        /// <returns>
        /// The open context menu.
        /// </returns>
        protected virtual bool OpenContextMenu(UIElement popupButton, Point ptMouse)
        {
            return false;
        }

        /// <summary>
        /// The handle move.
        /// </summary>
        protected virtual void HandleMove()
        {
            Point mousePosition = PointToScreen(Mouse.GetPosition(null));
            Point clickPoint = this.TransformToDeviceDPI(mousePosition);
            if (!Manager.DragPaneServices.IsDragging)
                Manager.Drag(this, clickPoint, new Point(clickPoint.X - Left, clickPoint.Y - Top));
        }

        #endregion

        #region Active Content Management

        /// <summary>
        /// The on activated.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected override void OnActivated(EventArgs e)
        {
            if (Manager != null)
            {
                Manager.ActiveContent = HostedPane.SelectedItem as ManagedContent;
            }

            base.OnActivated(e);
        }

        #endregion

        #region IsClosing Flag Management

        /// <summary>
        /// Gets a value indicating whether ForcedClosing.
        /// </summary>
        protected bool ForcedClosing { get; private set; }

        /// <summary>
        /// Gets a value indicating whether IsClosing.
        /// </summary>
        internal bool IsClosing { get; private set; }

        /// <summary>
        /// Closes the window regardless of result of contents CanClose method call
        /// </summary>
        /// <param name="force">
        /// </param>
        internal void Close(bool force)
        {
            ForcedClosing = force;
            this.Close();
        }

        /// <summary>
        /// The on closing.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected override void OnClosing(CancelEventArgs e)
        {
            IsClosing = true;

            if (HostedPane.Items.Count > 0)
            {
                ManagedContent[] contentsToClose = HostedPane.Items.Cast<ManagedContent>().ToArray();
                foreach (ManagedContent cntToClose in contentsToClose)
                {
                    // if even a content can't close than cancel the close process, but continue try closing other contents
                    if (!cntToClose.Close())
                    {
                        // forced closing continues the window close process
                        if (!ForcedClosing)
                            e.Cancel = true;
                    }
                }
            }

            if (e.Cancel)
                IsClosing = false;
            else if (_manager != null)
            {
                _manager.UnregisterFloatingWindow(this);
            }

            base.OnClosing(e);
        }

        /// <summary>
        /// The on closed.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected override void OnClosed(EventArgs e)
        {
            IsClosing = false;
            base.OnClosed(e);
        }

        #endregion
    }
}